 /*******************************************************************************
  * Copyright (c) 2000, 2005 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.forms.widgets;

 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.ScrolledComposite;
 import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.ScrollBar;
 import org.eclipse.ui.internal.forms.widgets.FormUtil;

 /**
  * This class is used to provide common scrolling services to a number of
  * controls in the toolkit. Classes that extend it are not required to implement
  * any method.
  *
  * @since 3.0
  */
 public abstract class SharedScrolledComposite extends ScrolledComposite {
     private static final int H_SCROLL_INCREMENT = 5;

     private static final int V_SCROLL_INCREMENT = 64;
     
     private boolean ignoreLayouts = true;

     private boolean ignoreResizes = false;

     private boolean expandHorizontal = false;

     private boolean expandVertical = false;

     private SizeCache contentCache = new SizeCache();

     private boolean reflowPending = false;

     private boolean delayedReflow = true;
     
     /**
      * Creates the new instance.
      *
      * @param parent
      * the parent composite
      * @param style
      * the style to use
      */
     public SharedScrolledComposite(Composite parent, int style) {
         super(parent, style);
         addListener(SWT.Resize, new Listener() {
             public void handleEvent(Event e) {
                 if (!ignoreResizes) {
                     scheduleReflow(false);
                 }
             }
         });
         initializeScrollBars();
     }

     /**
      * Sets the foreground of the control and its content.
      *
      * @param fg
      * the new foreground color
      */
     public void setForeground(Color fg) {
         super.setForeground(fg);
         if (getContent() != null)
             getContent().setForeground(fg);
     }

     /**
      * Sets the background of the control and its content.
      *
      * @param bg
      * the new background color
      */
     public void setBackground(Color bg) {
         super.setBackground(bg);
         if (getContent() != null)
             getContent().setBackground(bg);
     }

     /**
      * Sets the font of the form. This font will be used to render the title
      * text. It will not affect the body.
      */
     public void setFont(Font font) {
         super.setFont(font);
         if (getContent() != null)
             getContent().setFont(font);
     }

     /**
      * Overrides 'super' to pass the proper colors and font
      */
     public void setContent(Control content) {
         super.setContent(content);
         if (content != null) {
             content.setForeground(getForeground());
             content.setBackground(getBackground());
             content.setFont(getFont());
         }
     }

     /**
      * If content is set, transfers focus to the content.
      */
     public boolean setFocus() {
         boolean result;
         FormUtil.setFocusScrollingEnabled(this, false);
         if (getContent() != null)
             result = getContent().setFocus();
         else
             result = super.setFocus();
         FormUtil.setFocusScrollingEnabled(this, true);
         return result;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.swt.widgets.Composite#layout(boolean)
      */
     public void layout(boolean changed) {
         if (ignoreLayouts) {
             return;
         }

         ignoreLayouts = true;
         ignoreResizes = true;
         super.layout(changed);
         ignoreResizes = false;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.swt.custom.ScrolledComposite#setExpandHorizontal(boolean)
      */
     public void setExpandHorizontal(boolean expand) {
         expandHorizontal = expand;
         super.setExpandHorizontal(expand);
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.swt.custom.ScrolledComposite#setExpandVertical(boolean)
      */
     public void setExpandVertical(boolean expand) {
         expandVertical = expand;
         super.setExpandVertical(expand);
     }

     /**
      * Recomputes the body layout and the scroll bars. The method should be used
      * when changes somewhere in the form body invalidate the current layout
      * and/or scroll bars.
      *
      * @param flushCache
      * if <code>true</code>, drop the cached data
      */
     public void reflow(boolean flushCache) {
         Composite c = (Composite) getContent();
         Rectangle clientArea = getClientArea();
         if (c == null)
             return;

         contentCache.setControl(c);
         if (flushCache) {
             contentCache.flush();
         }
         setRedraw(false);
         Point newSize = contentCache.computeSize(FormUtil.getWidthHint(
                 clientArea.width, c), FormUtil.getHeightHint(clientArea.height,
                 c));

         // Point currentSize = c.getSize();
 if (!(expandHorizontal && expandVertical)) {
             c.setSize(newSize);
         }

         setMinSize(newSize);
         FormUtil.updatePageIncrement(this);

         ignoreLayouts = false;
         layout(flushCache);
         ignoreLayouts = true;

         contentCache.layoutIfNecessary();
         setRedraw(true);
     }

     private void updateSizeWhilePending() {
         Control c = getContent();
         Rectangle area = getClientArea();
         setMinSize(area.width, c.getSize().y);
     }

     private void scheduleReflow(final boolean flushCache) {
         if (delayedReflow) {
             if (reflowPending) {
                 updateSizeWhilePending();
                 return;
             }
             getDisplay().asyncExec(new Runnable () {
                 public void run() {
                     if (!isDisposed())
                         reflow(flushCache);
                     reflowPending = false;
                 }
             });
             reflowPending = true;
         } else
             reflow(flushCache);
     }

     private void initializeScrollBars() {
         ScrollBar hbar = getHorizontalBar();
         if (hbar != null) {
             hbar.setIncrement(H_SCROLL_INCREMENT);
         }
         ScrollBar vbar = getVerticalBar();
         if (vbar != null) {
             vbar.setIncrement(V_SCROLL_INCREMENT);
         }
         FormUtil.updatePageIncrement(this);
     }

     /**
      * Tests if the control uses delayed reflow.
      * @return <code>true</code> if reflow requests will
      * be delayed, <code>false</code> otherwise.
      */
     public boolean isDelayedReflow() {
         return delayedReflow;
     }

     /**
      * Sets the delayed reflow feature. When used,
      * it will schedule a reflow on resize requests
      * and reject subsequent reflows until the
      * scheduled one is performed. This improves
      * performance by
      * @param delayedReflow
      * The delayedReflow to set.
      */
     public void setDelayedReflow(boolean delayedReflow) {
         this.delayedReflow = delayedReflow;
     }
 }

